/*
 *
 * *******************************************************************
 *   @项目名称: BHex Android
 *   @文件名称: BHSocketController.java
 *   @Date: 11/29/18 3:21 PM
 *   @Author: chenjun
 *   @Copyright（C）: 2018 BlueHelix Inc.   All rights reserved.
 *   注意：本内容仅限于内部传阅，禁止外泄以及用于其他的商业目的.
 *  *******************************************************************
 *
 */

package io.bhex.sdk.socket;

import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;

import com.google.gson.Gson;

import java.nio.channels.NotYetConnectedException;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import io.bhex.baselib.constant.Fields;
import io.bhex.baselib.core.CApplication;
import io.bhex.baselib.network.HttpUtils;
import io.bhex.baselib.network.params.UploadParam;
import io.bhex.baselib.utils.DebugLog;
import io.bhex.baselib.utils.DevicesUtil;
import io.bhex.baselib.utils.JsonUtils;
import io.bhex.sdk.BhexSdk;
import io.bhex.sdk.Urls;
import io.bhex.sdk.account.UserInfo;
import io.bhex.sdk.data_manager.RateAndLocalManager;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

import static io.bhex.baselib.constant.AppData.Config.SOCKET_RETRY_DELAY;

public class BHSocketController {

    private Map<String, SocketResponseBack> requestMap;
    private String mUrl;
    private WebSocket hqSocketClient;
    //private WebSocket mOKSocketClient;
    private int mCallID = 0;
    private int RECONNECT_MESSAGE = 1;

    private boolean mNeedLogin = false;

    private Timer timer;
    private TimerTask timerTask;
    private boolean bSendPing = false;
    private long pingTime;

    private  Handler mHandler = new Handler(){
        public void handleMessage(Message msg){
            if(msg.what == RECONNECT_MESSAGE)
                connectSocket(mUrl);
        }
    };

    private boolean isOpen; //默认未打开
    private boolean isClosing;  //正在关闭
    private boolean isConnecting;   //正在连接
    private boolean isClose=false;   //默认是开启状态状态


    public class SocketResponseBack<T> {
        public Class<T> responseClass;
        public SocketResponseHandler<T> handler;
        public WebSocketBaseBean socketBaseBean;

    }

    public BHSocketController(String url, boolean needLogin) {
        requestMap = new ConcurrentHashMap<String, SocketResponseBack>();
        mUrl = url;
        mNeedLogin = needLogin;
        connectSocket(url);
    }

    public boolean isOpen() {
        if(hqSocketClient == null )
            return false;
        return isOpen;
    }

    public boolean isConnecting() {
        if(hqSocketClient == null )
            return false;
        return isConnecting;
    }

    public <T> int SubSocketApi(WebSocketBaseBean baseSocketBean,
                                 Class<T> responseClass,
                                 SocketResponseHandler<T> handler) {
        if (baseSocketBean == null)
            return mCallID;

        baseSocketBean.id = String.valueOf(++mCallID);
        Gson gson = new Gson();
        String sendData = gson.toJson(baseSocketBean);

        //DebugLog.e("BHSocketController==>:","===sendData==="+sendData);


        SendMessageWebSocket(sendData);

        if (handler != null) {
            registerSocketApi(baseSocketBean, responseClass, handler);
        }
        return mCallID;
    }


    public <T> void UnSubSocketApi(WebSocketBaseBean baseSocketBean) {
        if (baseSocketBean == null)
            return;

        baseSocketBean.event = "cancel";

        Gson gson = new Gson();
        String sendData = gson.toJson(baseSocketBean);
        SendMessageWebSocket(sendData);

        unRegisterSocketApi(baseSocketBean);

    }

    public <T> void UnSubSocketApi(int callID) {
        if (requestMap.containsKey(String.valueOf(callID))) {
            SocketResponseBack request = requestMap.get(String.valueOf(callID));
            WebSocketBaseBean baseSocketBean = request.socketBaseBean;

            if(baseSocketBean == null)
                return;
            baseSocketBean.id = String.valueOf(callID);
            baseSocketBean.event = "cancel";

            //Gson gson = new Gson();
            String sendData = JsonUtils.toJson(baseSocketBean);
            SendMessageWebSocket(sendData);

            unRegisterSocketApi(baseSocketBean);
        }


    }


    public <T> void registerSocketApi(WebSocketBaseBean baseSocketBean,
                                      Class<T> responseClass,
                                      SocketResponseHandler<T> handler) {


        if(requestMap.containsKey(baseSocketBean.id)){
            //Log.e("socket", "error registerSocketApi id " + baseSocketBean.id);
            requestMap.remove(baseSocketBean.id);
        }
        SocketResponseBack request = new SocketResponseBack();
        request.socketBaseBean = baseSocketBean;
        request.responseClass = responseClass;
        request.handler = handler;
        requestMap.put(baseSocketBean.id, request);
    }

    public <T> void unRegisterSocketApi(WebSocketBaseBean baseSocketBean) {
        if (baseSocketBean == null)
            return;
        if (requestMap.containsKey(baseSocketBean.id)) {
            SocketResponseBack request = requestMap.get(baseSocketBean.id);
            if (request != null && request.socketBaseBean != null && request.socketBaseBean.topic.equalsIgnoreCase(baseSocketBean.topic)){
                DebugLog.e("BHNetWorkController==>:","=baseSocketBean=id=="+ baseSocketBean.key);

                requestMap.remove(baseSocketBean.id);
            }

        }
    }

    public void SendMessageWebSocket(String msg) {
        if (hqSocketClient != null && isOpen) {
//            if(!msg.contains("ping")){
                DebugLog.e("socket==>:", "SendMessageWebSocket " + msg);
//            }

            if (!TextUtils.isEmpty(msg)) {
                try {
                    hqSocketClient.send(msg);
                } catch (NotYetConnectedException e) {
                    e.printStackTrace();
                }
            }
        } else {
            if (hqSocketClient == null || !isConnecting()){
                connectSocket(mUrl);
            }
        }

    }

    private void connectSocket(String url) {
        if (isConnecting || isOpen) {
            //已连接 不再进行重连
            return;
        }

        if(mNeedLogin == true && UserInfo.isLogin() == false){
            return;
        }

        //修改socket地址
        if (mNeedLogin) {
            mUrl = Urls.SOCKET_TRADE;
        }else{
            mUrl = Urls.SOCKET_QUOTE;
        }

        if (TextUtils.isEmpty(mUrl)) {
            return;
        }

        if(HttpUtils.getInstance().getHttpClient() != null) {

            Request.Builder builder = new Request.Builder().url(mUrl);

            builder.addHeader(Fields.PARAM_NETT, DevicesUtil.GetNetworkType(CApplication.getInstance()));

            String userId = "";
            if(UserInfo.getUserInfo() != null && !TextUtils.isEmpty(UserInfo.getUserInfo().getUserId()))
                userId = UserInfo.getUserInfo().getUserId();
            builder.addHeader(Fields.PARAM_UID, userId);
            String lan = RateAndLocalManager.GetInstance(CApplication.getInstance()).getCurLocalLanguage();
            if(!lan.isEmpty())
                builder.addHeader(Fields.PARAM_LANGUAGE, lan);

            Request request = builder.build();
            DebugLog.d("BHSocketController==> new websocket :",request.url().toString());
            isConnecting = true;
            hqSocketClient =   HttpUtils.getInstance().getHttpClient().newWebSocket(request, new WebSocketListener() {
                public void onOpen(WebSocket webSocket, Response response) {
                    isOpen = true;
                    isClose = false;
                    isConnecting = false;
                    bSendPing = false;
                    BHSocketController.this.onOpen();
                }
                public void onMessage(WebSocket webSocket, String text) {
                    bSendPing = false;
                    isConnecting = false;
                    BHSocketController.this.onMessage(text);
                }

                @Override
                public void onMessage(WebSocket webSocket, ByteString bytes) {
                    bSendPing = false;
                    isConnecting = false;
                    String message = GZIPUtils.uncompressToString(bytes.toByteArray(), "UTF-8");
                    BHSocketController.this.onMessage(message);
                }

                public void onClosing(WebSocket webSocket, int code, String reason) {
                    isConnecting = false;
                    isClosing = true;
                    bSendPing = false;
                    DebugLog.d("BHSocketController", "onClosing " + mUrl);
                }

                public void onClosed(WebSocket webSocket, int code, String reason) {
                    isConnecting = false;
                    isClose = true;
                    isClosing = false;
                    isOpen = false;
                    bSendPing = false;
                    DebugLog.d("BHSocketController", "onClosed " + mUrl);
                    BHSocketController.this.onClose(code,reason, false);
                }

                public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                    isConnecting = false;
                    isOpen = false;
                    isClosing = false;
                    isClose = true;
                    bSendPing = false;
                    if(response != null){
                        DebugLog.d("BHSocketController", "onFailure " + mUrl);
                        BHSocketController.this.onClose(response.code(),response.message(), true);
                    }else{
                        DebugLog.d("BHSocketController", "onFailure  no response " + mUrl);
                        String faileMessage = "";
                        if (t != null) {
                            faileMessage = t.getMessage();
                        }
                        BHSocketController.this.onClose(-2,faileMessage, true);
                    }
                }

            });

        }
        if(timer == null){
            startTimer();
        }
    }

    public void disConnectSocket() {
        //判空
        if (hqSocketClient != null) {
            hqSocketClient.cancel();
        }
        stopTimer();
    }

    public void release() {
        //判空
        stopTimer();
        if (hqSocketClient != null) {
            hqSocketClient.cancel();
        }
        hqSocketClient = null;
    }

    public void onMessage(String message) {
        HandlerSocketMessage(message);
    }

    private <T> void HandlerSocketMessage(String message) {
       DebugLog.v("socket==>:", "HandlerSocketMessage " + message);
//        if (BuildConfig.DEBUG) {
//            if (message.contains("diffDepth")) {
//                LogToFile.i("diffDepth",message);
//            }
//        }
        try {
            //Gson gson = new Gson();
            WebSocketBaseBean resultData = JsonUtils.fromJson(message, WebSocketBaseBean.class);
            if (resultData != null) {
                if( resultData.ping != null && !resultData.ping.isEmpty()){
                    WebSocketBaseBean pongBean = new WebSocketBaseBean();
                    pongBean.pong = resultData.ping;
                    String sendData = JsonUtils.toJson(pongBean);
                    SendMessageWebSocket(sendData);//回复pong
                }

                SocketResponseBack res = null;
                if(!TextUtils.isEmpty(resultData.id))
                    res = requestMap.get(resultData.id);
                if (res != null && res.handler != null) {//有id的不考虑topic
                    if (res.socketBaseBean != null && res.socketBaseBean.topic != null) {
                        res.socketBaseBean.sid = resultData.sid;
                        //if (res.socketBaseBean.topic.equalsIgnoreCase(resultData.topic)) {
                            if(!TextUtils.isEmpty(res.socketBaseBean.getCode()) && ! res.socketBaseBean.getCode().equalsIgnoreCase("200") && ! res.socketBaseBean.getCode().equalsIgnoreCase("0")){
                                res.handler.sendErrorMessage(res.socketBaseBean.getCode() + res.socketBaseBean.getMsg());
                            }
                            //Gson resGson = new Gson();
                            T result = (T) JsonUtils.fromJson(message, res.responseClass);
                            res.handler.sendSuccessMessage(result);
                            if(res.socketBaseBean.event.equalsIgnoreCase("req")){
                                unRegisterSocketApi(res.socketBaseBean);
                            }
                        //}
                    }
                }
                else if(res == null && TextUtils.isEmpty(resultData.id)){//兼容没有id的 考虑topic
                    for (String id : requestMap.keySet()) {

                        SocketResponseBack resNoid = requestMap.get(id);
                        if (resNoid != null && resNoid.handler != null) {
                            if (resNoid.socketBaseBean != null && resNoid.socketBaseBean.topic != null) {
                                resNoid.socketBaseBean.sid = resultData.sid;
                                if (resNoid.socketBaseBean.topic.equalsIgnoreCase(resultData.topic)) {
                                    if(!TextUtils.isEmpty(resNoid.socketBaseBean.getCode()) && ! res.socketBaseBean.getCode().equalsIgnoreCase("200") && ! res.socketBaseBean.getCode().equalsIgnoreCase("0")){
                                        resNoid.handler.sendErrorMessage(res.socketBaseBean.getCode() + resNoid.socketBaseBean.getMsg());
                                    }
                                    Gson resGson = new Gson();
                                    T result = (T) resGson.fromJson(message, resNoid.responseClass);
                                    resNoid.handler.sendSuccessMessage(result);
                                    if(resNoid.socketBaseBean.event.equalsIgnoreCase("req")){
                                        unRegisterSocketApi(resNoid.socketBaseBean);
                                    }
                                }
                            }
                        }
                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void onOpen() {
        DebugLog.e("BHSocketController", "onOpen=" + mUrl);
        if(requestMap != null && requestMap.size() > 0){
            reSubAllSocketApi(requestMap);
        }

    }

    public void reSubAllSocketApi(Map<String, SocketResponseBack> map){
        if(map != null && map.size() > 0){
            for (String id : map.keySet()) {

                SocketResponseBack res = map.get(id);
                if (res != null && res.socketBaseBean != null) {
                    Gson gson = new Gson();
                    String sendData = gson.toJson(res.socketBaseBean);
                    SendMessageWebSocket(sendData);
                }
            }
        }

    }

    public void onClose(int code, String reason, boolean error) {
        DebugLog.d("BHSocketController", "onClose " + mUrl + " "+ code + reason);
////        isOpen = false;
//        if (hqSocketClient != null) {
//            if (isOpen) {
//                hqSocketClient.cancel();
//            }
//            hqSocketClient = null;
//        }
        if (requestMap != null) {
            for (String id : requestMap.keySet()) {

                SocketResponseBack res = requestMap.get(id);
                if (res != null && res.handler != null) {
                    res.handler.sendErrorMessage(reason);
                    if(error) {
                        BhexSdk.ResponseErrorNotity(mUrl, String.valueOf(code) + ":" + reason);

                        UploadParam uploadParam = new UploadParam();
                        uploadParam.request_url = mUrl;
                        uploadParam.timestamp = String.valueOf(System.currentTimeMillis());
                        uploadParam.cost = "";
                        uploadParam.error_code = String.valueOf(code);
                        uploadParam.error_message = reason;
                        uploadParam.net = BhexSdk.netType;
                        uploadParam.success = false;
                        BhexSdk.NetRequestNotity(uploadParam);
                    }
                }
            }
        }

        mHandler.removeMessages(RECONNECT_MESSAGE);
        mHandler.sendEmptyMessageDelayed(RECONNECT_MESSAGE, SOCKET_RETRY_DELAY);
//        mHandler.postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                connectSocket(mUrl);
//            }
//        },SOCKET_RETRY_DELAY);
    }

    public void onError(Exception ex) {
        DebugLog.d("BHSocketController", "onError" + ex.getMessage());
        if (requestMap != null) {
            for (String id : requestMap.keySet()) {
                SocketResponseBack res = requestMap.get(id);
                if (res != null && res.handler != null) {
                    res.handler.sendErrorMessage(ex.getMessage());
                }

            }
        }
    }

    private<T> void startTimer() {
        timer = new Timer();
        timerTask = new TimerTask() {

            @Override
            public void run() {
                WebSocketBaseBean bean = new WebSocketBaseBean();
                bean.ping = String.valueOf(System.currentTimeMillis());
                Gson gson = new Gson();
                String sendData = gson.toJson(bean);
                SendMessageWebSocket(sendData);
                DebugLog.d("BHSocketController", "local ping " + mUrl );
//                if(bSendPing == true && System.currentTimeMillis() - pingTime > 10000){
                if(bSendPing == true && System.currentTimeMillis() - pingTime > 4000){
                    bSendPing = false;
                    //TODO 测试 ping timeout 主动取消socket
                    if (hqSocketClient != null) {
                        DebugLog.d("BHSocketController", "ping timeout 主动 cancel socket通道 " + mUrl );
                        hqSocketClient.cancel();
                    }
                    BHSocketController.this.onClose(4001,"local ping timeout client want close socket", true); //自定义关闭（ping 超时）
                }
                else {
                    bSendPing = true;
                    pingTime = System.currentTimeMillis();
                }
            }
        };
//        timer.schedule(timerTask, 11000, 11000);
        timer.schedule(timerTask, 5000, 5000);
    }

    private void stopTimer(){
        if(timerTask != null)
            timerTask.cancel();
        if(timer != null) {
            //timer.purge();
            timer.cancel();
        }
        timer = null;
        timerTask = null;
    }

}
